/**
 * Copyright 2012 Roger Cockshutt
 * krakenbrau@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.parallelrl.data;

import java.util.HashMap;
import java.util.List;
import java.util.Vector;

import com.googlecode.parallelrl.data.ecs.ECSManager;
import com.googlecode.parallelrl.display.Colors;
import com.googlecode.parallelrl.process.BehaviourManager;
import com.googlecode.parallelrl.process.ECSTools;
import com.googlecode.parallelrl.util.ColoredChar;
import com.googlecode.parallelrl.util.ComponentPair;
import com.googlecode.parallelrl.util.Point2D;

/**
 * @author Roger Cockshutt
 *
 * Instances of this class contain information about a single dungeon level, and everything contained within.
 */
public class ECSLevel implements ILevel {
	
	private final List<Long>								entityList;
	private final HashMap<String, String>					terrainGrid;
	private final HashMap<String, Long> 					terrainAssemblageIds;
	private final Point2D									size;
		

	/**
	 * Create a new level of the given size.
	 */
	public ECSLevel(Point2D levelSize) {
		size = levelSize;
		entityList = new Vector<>();
		terrainAssemblageIds = new HashMap<>();
		terrainGrid = new HashMap<>();
		initgrid();
	}
	
	public ECSLevel(Integer y, Integer x) {
		size = new Point2D(y, x);
		entityList = new Vector<>();
		terrainAssemblageIds = new HashMap<>();
		terrainGrid = new HashMap<>();
	}
	
	private ECSLevel(Point2D levelSize, HashMap<String, Long> asmblgs, HashMap<String, String> grid) {
		size = levelSize;
		terrainGrid = grid;
		terrainAssemblageIds = asmblgs;
		entityList = new Vector<>();
	}
	
	public static ILevel valueOf(String[] lines) {
		Point2D size = null;
		HashMap<String, Long> asmblgIDs = new HashMap<>();
		HashMap<String, String> grid = new HashMap<>();
		for (int i = 0; i < lines.length; i++) {
			String[] tests = lines[i].split(":");
			if (tests.length > 1) {
				if (tests[0].trim().equals("SIZE")) {
					tests = lines[i].split("'");
					if (tests.length >= 2) {
						size = Point2D.valueOf(tests[1].trim());
					}
				} else {
					String name = tests[0].trim();
					tests = lines[i].split("'");
					if (tests.length >= 2) {
						asmblgIDs.put(name, Long.valueOf(tests[1].trim()));
						ECSManager.createEntityFromAssemblage(name, Long.valueOf(tests[1].trim()));
						boolean keepGoing = true;
						while (keepGoing) {
							tests = lines[++i].split("[\\[\\]]");
							if (tests.length >= 2) {
								grid.put(tests[1], name);
							} else {
								keepGoing = false;
								i--;
							}
							if (i + 1 == lines.length) {
								keepGoing = false;
							}
						}
					}
				}
			}
		}
		if (size != null) {
			return new ECSLevel(size, asmblgIDs, grid);
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see com.googlecode.parallelrl.data.ILevel#toString()
	 */
	@Override
	public String toString() {
		String str = "LEVEL{\n";
		str += "  SIZE: '" + size.toString() + "'\n";
		HashMap<String, String> typeStrs = new HashMap<>();
		for (String terrainType : terrainAssemblageIds.keySet()) {
			String substr = "  " + terrainType + ": '" + terrainAssemblageIds.get(terrainType) + "'\n";
			typeStrs.put(terrainType, substr);
		}
		for (String posStr : terrainGrid.keySet()) {
			String substr = typeStrs.get(terrainGrid.get(posStr));
			if (substr != null) {
				substr += "    [" + posStr + "]\n";
			}
			typeStrs.put(terrainGrid.get(posStr), substr);
		}
		for (String typeDef : typeStrs.values()) {
			str += typeDef;
		}
		return str + "}\n";
	}
	
	/* (non-Javadoc)
	 * @see com.googlecode.parallelrl.data.ILevel#addEntityAt(java.lang.Integer, com.googlecode.parallelrl.util.Point2D)
	 */
	@Override
	public boolean addEntityAt(Long entity, Point2D position) {
		 return setPositionOf(entity, position);
	}
	
	public static List<Long> getEntitiesAt(Point2D position) {
		List<Long> entities = ECSManager.getAllEntitiesPossessingComponentValue("Position", position.toString());
		return entities;
	}
	
	public static Long getFirstEntityWith(Point2D position, String component, String value) {
		List<Long> entities = getEntitiesAt(position);
		if (value != null) {
			entities = ECSTools.selectForComponentValue(entities, component, value, false);
		} else {
			entities = ECSTools.selectForComponent(entities, component);
		}
		if (!entities.isEmpty()) {
			return entities.get(0);
		}
		return null;
	}
	
	public static List<Long> getEntitiesAt(Integer y, Integer x) {
		return getEntitiesAt(new Point2D(y, x));
	}
	
	/* (non-Javadoc)
	 * @see com.googlecode.parallelrl.data.ILevel#isMovableTo(com.googlecode.parallelrl.util.Point2D)
	 */
	@Override
	public boolean isMovableTo(Point2D position) {
		if (position == null
				|| position.y >= size.y
				|| position.x >= size.x
				|| position.y < 0
				|| position.x < 0) {
			return false;
		}
		Long terrainTypeId = terrainAssemblageIds.get(terrainGrid.get(position.toString()));
		if (ECSManager.hasComponent(terrainTypeId, "Impassable")) {
			return false;
		}
		Long entity = getFirstEntityWith(position, "Impassable", null);
		if (entity != null) {
			return false;
		}
		return true;
	}
	
	/* (non-Javadoc)
	 * @see com.googlecode.parallelrl.data.ILevel#isMovableTo(java.lang.Integer, java.lang.Integer)
	 */
	@Override
	public boolean isMovableTo(Integer y, Integer x) {
		return isMovableTo(new Point2D(y, x));
	}
	
	/* (non-Javadoc)
	 * @see com.googlecode.parallelrl.data.ILevel#setPositionOf(java.lang.Integer, com.googlecode.parallelrl.util.Point2D)
	 */
	@Override
	public boolean setPositionOf(Long entity, Point2D newPosition) {
		if (entity == null
				|| !isMovableTo(newPosition)) {
			return false;
		}
		if (!entityList.contains(entity)) {
			entityList.add(entity);
		}
		
		ComponentPair position = ECSManager.getComponent(entity, "Position");
		position = new ComponentPair("Position", newPosition.toString());
		ECSManager.setComponent(entity, position);
		//GameLogger.get().addMessage("ID " + entity + ": " + newPosition.toString(), Colors.libTCOD.white.normal);
		return true;
	}
	
	/**
	 * Return the current position of an entity.
	 * 
	 * @param entity The entity in question.
	 * @return a CoordinatePair representing the entity's position, or null if the entity has no position.
	 */
	public static Point2D getPositionOf(Long entity) {
		ComponentPair position = ECSManager.getComponent(entity, "Position");
		if (position == null) {
			return null;
		}
		return position.coordinateValue();
	}
	
	private ColoredChar getDisplayCharFor(Point2D pos) {
		ColoredChar dChar = null;
		List<Long> entities = getEntitiesAt(pos);
		Long terrainID = terrainAssemblageIds.get(terrainGrid.get(pos.toString()));
		if (terrainID == null) {
			dChar = new ColoredChar(' ', Colors.libTCOD.white.normal);
		} else {
			Integer color = ECSManager.getComponent(terrainID, "Color").intValue();
			Character dispchar = ECSManager.getComponent(terrainID, "DisplayCharacter").charValue();
			dChar = new ColoredChar(dispchar, color);
		}
		entities = ECSTools.selectForComponent(entities, "DisplayCharacter");
		if (entities != null && !entities.isEmpty()) {
			Integer color = ECSManager.getComponent(entities.get(0), "Color").intValue();
			Character dispchar = ECSManager.getComponent(entities.get(0), "DisplayCharacter").charValue();
			dChar = new ColoredChar(dispchar, color);
		}
		return dChar;
	}
	
	/* (non-Javadoc)
	 * @see com.googlecode.parallelrl.data.ILevel#getDisplayRegion(com.googlecode.parallelrl.util.Point2D)
	 */
	@Override
	public ColoredChar[][] getDisplayRegion(Point2D displaySize) {
		Long playerID = BehaviourManager.getLastActiveControllable();
		if (playerID == null) {
			return null;
		}
		int starty = getPositionOf(playerID).y - (displaySize.y / 2);
		int startx = getPositionOf(playerID).x - (displaySize.x / 2);
		int endy = starty + displaySize.y;
		int endx = startx + displaySize.x;
		
		ColoredChar[][] displayRegion = new ColoredChar[displaySize.y][displaySize.x];
		
		for (int iy = starty; iy < endy; iy++) {
			for (int ix = startx; ix < endx; ix++) {
				if (iy < 0
						|| ix < 0
						|| iy >= size.y
						|| ix >= size.x) {
					displayRegion[iy - starty][ix - startx] = new ColoredChar(' ', Colors.libTCOD.white.normal);
				} else {
					displayRegion[iy - starty][ix - startx] = getDisplayCharFor(new Point2D(iy, ix));
				}
			}
		}
		
//		List<Integer> entities = ECSManager.getAllEntitiesPossessingComponent("Position");
//		entities.retainAll(ECSManager.getAllEntitiesPossessingComponent("DisplayCharacter"));
//		for (Integer entity : entities) {
//			CoordinatePair position = getPositionOf(entity);
//			if (!(position.getY() < starty
//					|| position.getX() < startx
//					|| position.getY() >= endy
//					|| position.getX() >= endx)) {
//				displayRegion[position.getY() - starty][position.getX() - startx] =
//						new ColoredChar(ECSManager.getComponent(entity, "DisplayCharacter")._value.charAt(0),
//								Integer.valueOf(ECSManager.getComponent(entity, "Color")._value));
//			}
//		}
		
		return displayRegion;
	}
	
	/* (non-Javadoc)
	 * @see com.googlecode.parallelrl.data.ILevel#getSize()
	 */
	@Override
	public Point2D getSize() {
		return this.size;
	}
	
	/**
	 * This method should probably call the dungeon level generator, when it's implemented.
	 * Sets up the new level.
	 */
	private void initgrid() {
		terrainAssemblageIds.put("WALL", ECSManager.createEntityFromAssemblage("WALL"));
		terrainAssemblageIds.put("FLOOR", ECSManager.createEntityFromAssemblage("FLOOR"));
		for (int iy = 0; iy < getSize().y; iy++) {
			for (int ix = 0; ix < getSize().x; ix++) {
				String terrain = null;
				if (ix == 0
						|| iy == 0
						|| ix == getSize().x - 1
						|| iy == getSize().y - 1) {
					terrain = "WALL";
				} else {
					terrain = "FLOOR";
				}
				terrainGrid.put(new Point2D(iy, ix).toString(), terrain);
			}
		}
		
	}

}
